package fr.pud.client.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Observable;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import fr.pud.client.i18n.LanguagesConstants;
import fr.pud.utils.DOMParser;
import fr.pud.utils.Tools;

/**
 * The representation of a fleet as a list of vehicle.
 * Apr 9, 2012 - OSE.
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         Fleet.java
 */
public class Fleet extends Observable implements Serializable,
        Iterable<Vehicle> {
    public final static String[] FLEET_HEADER     = {
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_NAME),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_CAPACITY),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_AUTONOMY),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_STARTING_POINT_NAME),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_STARTING_POINT_ADDRESS),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.STATION_OPENING),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.STATION_CLOSING),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_ENDING_POINT_NAME),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE_ENDING_POINT_ADDRESS),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.STATION_OPENING),
            LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.STATION_CLOSING)
                                                  };
    /**
     * A serial UID corresponding to the fleet class
     */
    private static final long    serialVersionUID = -1902571414803163373L;

    /**
     * @return the instance
     */
    public static Fleet getInstance() {
        initialization();
        return instance;
    }

    /**
     * Initialize the singleton.
     */
    private static void initialization() {
        if (instance == null) {
            instance = new Fleet();
        }
    }

    /**
     * Load a Fleet
     * @return an arrayList of fleet
     */
    @SuppressWarnings("unchecked")
    public static ArrayList<Vehicle> loadFleet(File aFile) {
        ArrayList<Vehicle> toReturn = new ArrayList<Vehicle>();
        if (aFile.exists()) {
            try {
                FileInputStream fis = new FileInputStream(aFile);
                ObjectInputStream ois = new ObjectInputStream(fis);
                try {
                    toReturn = (ArrayList<Vehicle>) ois.readObject();
                }
                finally {
                    try {
                        ois.close();
                    }
                    finally {
                        fis.close();
                    }
                }
            }
            catch (IOException ioe) {
                Tools.writeToLogFile(
                        LanguagesConstants.LANGUAGE
                                .getString(LanguagesConstants.ERROR_FILE)
                                + " : " + aFile, true, ioe.getStackTrace());
            }
            catch (ClassNotFoundException cnfe) {
                Tools.writeToLogFile(
                        LanguagesConstants.LANGUAGE
                                .getString(LanguagesConstants.ERROR_FILE)
                                + " : " + aFile, true, cnfe.getStackTrace());
            }
        }
        return toReturn;
    }

    /**
     * The arraylist of vehicle.
     */
    protected ArrayList<Vehicle> vehicles = new ArrayList<Vehicle>();
    /**
     * The representation of a singleton.
     */
    private static Fleet         instance = null;

    /**
     * Default constructor
     */
    private Fleet() {
    }

    /**
     * @param v
     *            the vehicle to add.
     * @return true if the adding worked.
     */
    public boolean add(Vehicle v) {
        initialization();
        if (this.vehicles.add(v)) {
            this.setChanged();
            this.notifyObservers();
            return true;
        }
        return false;
    }

    /**
     * @return the ArrayList<Vehicle>.
     */
    public ArrayList<Vehicle> getVehicles() {
        initialization();
        return this.vehicles;
    }

    /**
     * @return the number of vehicles.
     */
    public int getVehiclesNb() {
        initialization();
        return this.vehicles.size();
    }

    /**
     * @return the iterator on the fleet.
     */
    @Override
    public Iterator<Vehicle> iterator() {
        initialization();
        return new Iterator<Vehicle>() {
            int index = 0;

            @Override
            public boolean hasNext() {
                return this.index < Fleet.this.vehicles.size();
            }

            @Override
            public Vehicle next() {
                this.index++;
                return Fleet.this.vehicles.get(this.index - 1);
            }

            @Override
            public void remove() {
            }
        };
    }

    /**
     * @param index
     *            the index of the vehicle to remove
     * @return true if the vehicle has been removed.
     */
    public boolean remove(int index) {
        initialization();
        if (this.vehicles.remove(index) != null) {
            this.setChanged();
            this.notifyObservers();
            return true;
        }
        return false;
    }

    /**
     * @param v
     *            the vehicle to remove
     * @return true if the vehicle has been removed.
     */
    public boolean remove(Vehicle v) {
        initialization();
        if (this.vehicles.remove(v)) {
            this.setChanged();
            this.notifyObservers();
            return true;
        }
        return false;
    }

    /**
     * Notify the observers that a vehicle has changed.
     */
    public void setFleet() {
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * @param fleet
     *            the new fleet.
     */
    public void setFleet(ArrayList<Vehicle> fleet) {
        initialization();
        for (Vehicle v : this.vehicles) {
            this.remove(v);
        }
        for (Vehicle f : fleet) {
            this.add(f);
        }
    }

    /**
     * @return a String representation of the Fleet.
     */
    public String toHTML() {
        String toReturn = "";
        int index = 1;
        for (Vehicle v : this.getVehicles()) {
            toReturn += LanguagesConstants.LANGUAGE
                    .getString(LanguagesConstants.VEHICLE)
                    + " : "
                    + index++
                    + "<br/>" + v.toHTML() + "<br/>";
        }
        return toReturn;
    }

    /**
     * @return a String representation of the Fleet.
     */
    @Override
    public String toString() {
        String toReturn = "";
        int index = 1;
        for (Vehicle v : this.getVehicles()) {
            toReturn += "<h3 style=\"color:blue\">"
                    + LanguagesConstants.LANGUAGE
                            .getString(LanguagesConstants.VEHICLE) + " : </h3>"
                    + index++ + "\n" + v.toHTML() + "\n";
        }
        return toReturn;
    }

    /**
     * @return a new XML document which contain the fleet.
     */
    public Document toXML() {
        Document d = DOMParser.getEmptyDOMTree();
        Element root = this.toXML(d);
        d.appendChild(root);
        return d;
    }

    /**
     * @param d
     *            the document which should contain the new xml element
     * @return an Element which can be bound into a xml document.
     */
    public Element toXML(Document d) {
        return this.toXML(d, "fleet");
    }

    /**
     * @param d
     *            the document which should contain the new xml element
     * @param name
     *            the name of the root element.
     * @return the element which can be bound into a xml document
     */
    public Element toXML(Document d, String name) {
        Element root = d.createElement(name);
        Iterator<Vehicle> it = this.vehicles.iterator();
        while (it.hasNext()) {
            root.appendChild(it.next().toXML(d));
        }
        return root;
    }

    /**
     * Write the xml document into a file with the name : fileName.
     * @param fileName
     *            the name of the file where the data are going to be stored.
     */
    public void toXML(String fileName) {
        DOMParser.writeOnDisk(this.toXML(), fileName);
    }
}
